<?php
/**
 * RPC Client
 * 
 * @category	XML
 * @package		dayscript.xml.rpc
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource	
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */

namespace dayscript\xml\rpc;

/**
 * RPC Client
 *
 * @category	XML
 * @package		dayscript.xml.rpc
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\xml\rpc\Request, \dayscript\xml\rpc\SocketException, \dayscript\xml\rpc\Exception, \dayscript\log\LogManager
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */
class Client	{
	private $server;
	private $port;
	private $path;
	private $useragent;
	private $response;
	private $message;
	
	const EOL = "\r\n";
	
	// Storage place for an error message
	private $error = false;
	
	function __construct( $server, $path = null, $port = 80 )	{
		if ( $path == null ) {
			$parts = \parse_url( $server );
			$this->server = $parts['host'];
			$this->port = ( isset( $parts['port'] ) && $parts['port'] ? $parts['port'] : $port );
			$this->path = ( isset( $parts['path'] ) && $parts['path'] ? $parts['path'] : '/' );
		}
		else	{
			$this->server = $server;
			$this->path = ( $path ? $path : '/' );
			$this->port = $port;
		}
		$this->useragent = 'Dayscript XML-RPC PHP Library';
		
		$this->log( 'RPC Client: "' . $this->server . ':' . $this->port . $this->path . '" ' . $this->useragent );
	}
	/**
	 * RPC method call
	 * 
	 * @param string $method
	 * @param mixed $args
	 * @return boolean $success
	 * @todo Add cache
	 */
	function call( $method, $args = null )	{
		$RPCRequest = new \dayscript\xml\rpc\Request( $method, $args );
		$length = $RPCRequest->getLength( );
		$xml = $RPCRequest->getXML( );
		
		$this->log( 'RPC Client request method: ' . $method );
		
		$request =  'POST ' . $this->path . ' HTTP/1.0' . Client::EOL;
		$request .= 'Host: ' . $this->server . Client::EOL;
		$request .= 'Content-Type: text/xml' . Client::EOL;
		$request .= 'User-Agent: ' . $this->useragent . Client::EOL;
		$request .= 'Content-length: ' . $length . Client::EOL . Client::EOL;
		$request .= $xml;
		
		$this->log( 'RPC Client request: {length=' . \strlen( $request ) . '}' . "\n" . $request . "\n" );
		
		$errno = 0;
		$errstr = '';
		$handle = \fsockopen( $this->server, $this->port, $errno, $errstr );
		
		if ( !$handle )
			throw new \dayscript\xml\rpc\SocketException( 'Could not open socket: ' . $errstr, $errno );
		
		$writen = \fputs( $handle, $request );
		if( !$writen )	{
			\fclose( $handle );
			throw new \dayscript\xml\rpc\SocketException( 'Could not write socket resource "' . $handle . '" ', -32300 );
		}
		
		$headers = array( );
		$lines = array( );
		while( !\feof( $handle ) )	{
			$line = trim( \fgets( $handle, 4096 ) );
			if( $line )
				$lines[] = $line;
			else if( $headers == null )	{
				$headers = $lines;
				$lines = array( );
			}
		}
		\fclose( $handle );
		
		$header = \implode( "\n", $headers );
		$content = \implode( "\n", $lines );
		$response = $header . "\n\n" . $content;
				
		$this->log( 'RPC Client response: {length=' . \strlen( $response ) . '}' . "\n" . $response );
		
		if( \count( $headers ) == 0 )	{
			$this->error = new \dayscript\xml\rpc\Exception( 'Empty HTTP response for method "' . $method . '"', -32700 );
			return false;
		}
		if( \stripos( $headers[0], '200 ok' ) === false )	{
			$this->error = new \dayscript\xml\rpc\Exception( 'Unknown HTTP status "' . $headers[0] . '" expected "HTTP/X.X 200 OK"', -32300 );
			return false;
		}
		
		// Now parse what we've got back
		$this->message = new \dayscript\xml\rpc\Message( $response );
		try	{
			$parsed = $this->message->parse( );
			if ( !$parsed )	{
				$this->error = new \dayscript\xml\rpc\Exception( 'Parse error. Response not well formed.', -32700 );
				return false;
			}
		}
		catch ( \Exception $e )	{
			$this->error = $e;
			return false;
		}
		
		if ( $this->message->getType( ) == \dayscript\xml\rpc\Message::TYPE_FAULT )	{
			$this->error = new \dayscript\xml\rpc\Exception( $this->message->faultString, $this->message->faultCode );
			return false;
		}
		return true;
	}
	private function log( $str )	{
		\dayscript\log\LogManager::appLog( \htmlentities( $str ), \dayscript\log\LogManager::LOG_DATASOURCE );		
	}
	function getResponse( ) {
		// methodResponses can only have one param - return that.
		if( $this->message )	{
			$params = $this->message->getParams( );
			if( !empty( $params ) )
				return $params[0];
		}
		return null;
	}
	function isError( ) {
		return ( \is_object( $this->error ) );
	}
	function getErrorCode( ) {
		return $this->error->getCode( );
	}
	function getErrorMessage( ) {
		return $this->error->getMessage( );
	}
	
}
